import { $, Service, Schema, makeArray } from "koishi"

const config = {
  defaultCostCurrency: ["bonus", "default"],
  profileCurrencyNames: {
    default: "点数",
    bonus: "点数消耗减免额度",
  },
}

class Monetary extends Service {
  static inject = ['database']

  constructor(ctx) {
    super(ctx, 'monetary', true)
    ctx.model.extend('monetary', {
      uid: 'unsigned',
      currency: 'string',
      value: 'unsigned',
    }, {
      primary: ['uid', 'currency'],
    })

    ctx.schema.extend('command', Schema.object({
      cost: Schema.computed(Number).default(0).description('每次调用的花费。'),
    }), 900)

    ctx.i18n.define("zh-CN", {
      general: {
        "insufficient-balance": '你没有足够的点数。',
      },
      commands: {
        help: {
          messages: {
            "command-cost": "花费：{0} 点数",
          },
        },
      },
    })

    ctx.on('command/before-execute', async ({ session, options, command }) => {
      for (const { name, notUsage } of Object.values(command._options)) {
        if (name in options && notUsage) return
      }
      const cost = session.resolve(command.config.cost) ?? 0
      if (!cost) return
      try {
        await this.cost(session.user.id, cost)
      } catch (e) {
        if (!(e instanceof Monetary.InsufficientBalanceError)) throw e
        return session.text("general.insufficient-balance")
      }
    })

    // extend command help
    ctx.on('help/command', (output, command, session) => {
      const cost = session.resolve(command.config.cost) ?? 0
      if (cost > 0)
        output.push(session.text("commands.help.messages.command-cost", [cost]))
    })

    ctx.inject(['profile'], (ctx) => {
      ctx.profile.register(async (session) => {
        const data = await this.query(session.user.id, Object.keys(config.profileCurrencyNames))
        return Object.entries(config.profileCurrencyNames).map(
          ([c, n]) => `${n}：${data[c] ?? 0}`
        )
          .join("，")
      }, ['id'])
    })
  }

  async cost(uid, cost, currency) {
    currency ??= config.defaultCostCurrency
    const data = await this.query(uid, makeArray(currency))
    if (Object.values(data).reduce((a, c) => a + c, 0) < cost)
      throw new Monetary.InsufficientBalanceError()
    if (Array.isArray(currency) && currency.length > 1)
      for (const c of currency.slice(0, -1)) {
        if (!data[c]) continue
        if (cost <= data[c]) {
          await this._cost(uid, cost, c)
          return
        }
        await this._cost(uid, data[c], c)
        cost -= data[c]
      }
    const lc = Array.isArray(currency) ? currency.at(-1) : currency
    await this._cost(uid, cost, lc)
  }

  /** @private */
  async _cost(uid, cost, currency) {
    await this.ctx.database.set('monetary', {
      uid,
      currency,
    }, (row) => ({
      value: $.sub(row.value, cost),
    }))
  }

  async gain(uid, gain, currency = 'default') {
    await this.ctx.database.upsert('monetary', (row) => [{
      uid,
      currency,
      value: $.add(row.value, gain),
    }])
  }

  async query(uid, currency = 'default') {
    const data = await this.ctx.database.get('monetary', {
      uid,
      ...(currency == null ? null : { currency }),
    })
    if (Array.isArray(currency))
      return Object.fromEntries(
        data.map(({ currency, value }) => [currency, value])
      )
    return data[0]?.value ?? 0
  }
}

Monetary.Config = Schema.object({})

Monetary.InsufficientBalanceError = class InsufficientBalanceError extends Error {}

export default Monetary
